package com.my.core.util.geo;


import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;

public class DistanceUtils {
    public static final class MapU {
        public static final double EARTH_RADIUS = 6378.137;
        public static final int N_2 = 2;
        public static final int N_1000 = 1000;
        public static final double N_180 = 180.0;
    }

    /**
     * 判断一个点是否在圆形区域内
     *
     * @param pointLon  判断点经度
     * @param pointLat  判断点维度
     * @param centerLon 圆心经度
     * @param centerLat 圆心维度
     * @param radius    半径
     * @return boolean
     */
    public static boolean isInCircle(double pointLon, double pointLat, double centerLon, double centerLat, double radius) {
        return getDistance(pointLon, pointLat, centerLon, centerLat) < radius;
    }

    /**
     * 判断是否在多边形区域内
     *
     * @param pointLon 要判断的点的经度
     * @param pointLat 要判断的点的维度
     * @param lon      区域各顶点的经度数组
     * @param lat      区域各顶点的维度数组
     * @return boolean
     */
    public static boolean isInPolygon(double pointLon, double pointLat, double[] lon, double[] lat) {
        // 将要判断的横纵坐标组成一个点
        Point2D.Double point = new Point2D.Double(pointLon, pointLat);
        // 将区域各顶点的横纵坐标放到一个点集合里面
        List<Point2D.Double> pointList = new ArrayList<>();
        double polygonPoint_x, polygonPoint_y;
        for (int i = 0; i < lon.length; i++) {
            polygonPoint_x = lon[i];
            polygonPoint_y = lat[i];
            Point2D.Double polygonPoint = new Point2D.Double(polygonPoint_x, polygonPoint_y);
            pointList.add(polygonPoint);
        }
        return areaCheck(point, pointList);
    }

    /**
     * 判断是否在多边形区域内
     *
     * @param point   要判断的点
     * @param polygon 区域点集合
     * @return boolean
     */
    public static boolean areaCheck(Point2D.Double point, List<Point2D.Double> polygon) {
        java.awt.geom.GeneralPath generalPath = new java.awt.geom.GeneralPath();
        Point2D.Double first = polygon.get(0);
        // 通过移动到指定坐标（以双精度指定），将一个点添加到路径中
        generalPath.moveTo(first.x, first.y);
        polygon.remove(0);
        for (Point2D.Double d : polygon) {
            // 通过绘制一条从当前坐标到新指定坐标（以双精度指定）的直线，将一个点添加到路径中。
            generalPath.lineTo(d.x, d.y);
        }
        // 将几何多边形封闭
        generalPath.lineTo(first.x, first.y);
        generalPath.closePath();
        // 测试指定的 Point2D 是否在 Shape 的边界内。
        return generalPath.contains(point);
    }

    /**
     * 获取两个点间的距离
     *
     * @param lonA1 A1点的经度
     * @param latA1 A1点的纬度
     * @param lonA2 A2点的经度
     * @param latA2 A2点的纬度
     * @return double
     */
    public static double getDistance(double lonA1, double latA1, double lonA2, double latA2) {
        // 单位(米)
        double lon1 = lonA1 * Math.PI / MapU.N_180;
        double lat1 = latA1 * Math.PI / MapU.N_180;
        double lon2 = lonA2 * Math.PI / MapU.N_180;
        double lat2 = latA2 * Math.PI / MapU.N_180;
        double dlon = lon2 - lon1;
        double dlat = lat2 - lat1;
        double a = Math.pow(Math.sin(dlat / MapU.N_2), MapU.N_2) +
                Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon / MapU.N_2), MapU.N_2);
        double c = MapU.N_2 * Math.asin(Math.sqrt(a));
        return c * MapU.EARTH_RADIUS * MapU.N_1000;
    }

    public static boolean isCoinCode(List<Location> locationList, List<Location> locationList2) {
        //获取四边形的线段
        List<Line> lineList = getLines(locationList);
        //获取三角形的线段
        List<Line> lineList2 = getLines(locationList2);
        //判断是否相交
        boolean isIntersect = isIntersect(lineList, lineList2);
        if (isIntersect) return true;
        //如果不相交判断是否包含-由于没有相交线段只要存在点在多边形内就说明包含
        boolean isPolygonInPolygon = isPolygonInPolygon(locationList, locationList2);
        if (isPolygonInPolygon) return true;
        return false;
    }

    private static boolean isPolygonInPolygon(List<Location> locationList, List<Location> locationList2) {
        //判断第一个多边形是否在第二个多边形内
        for (Location location : locationList) {
            boolean isPointInPolygon = isPointInPolygon(location, locationList2);
            if (isPointInPolygon) return true;
        }
        //判断第二个多边形是否在第一个多边形内
        for (Location location : locationList2) {
            boolean isPointInPolygon = isPointInPolygon(location, locationList);
            if (isPointInPolygon) return true;
        }
        return false;
    }

    private static boolean isPointInPolygon(Location location, List<Location> locationList2) {
        //点是否在多边形内
        GeneralPath path = new GeneralPath();
        //设定多边形起始点
        path.moveTo(locationList2.get(0).getLongitude(), locationList2.get(0).getLatitude());
        for (Location l : locationList2) {
            path.lineTo(l.getLongitude(), l.getLatitude());
        }
        //图像完成，封闭
        path.moveTo(locationList2.get(0).getLongitude(), locationList2.get(0).getLatitude());
        //多边形结束
        path.closePath();
        return path.contains(location.getLongitude(), location.getLatitude());
    }

    private static boolean isIntersect(List<Line> lineList, List<Line> lineList2) {
        for (Line line : lineList) {
            for (Line line1 : lineList2) {
                //两条线段是否相交
                boolean b = Line2D.linesIntersect(line.location1.longitude, line.location1.latitude, line.location2.longitude, line.location2.latitude,
                        line1.location1.longitude, line1.location1.latitude, line1.location2.longitude, line1.location2.latitude);
                if (b) {
                    return true;
                }
            }
        }
        return false;
    }

    private static List<Line> getLines(List<Location> locationList) {
        List<Line> lineList = new ArrayList<>();
        for (int i = 0; i < locationList.size(); i++) {
            if (i < locationList.size() - 1) {
                Location l = locationList.get(i);
                Location l2 = locationList.get(i + 1);
                Line line = new Line(l, l2);
                lineList.add(line);
            } else {
                Location l = locationList.get(i);
                Location l2 = locationList.get(0);
                Line line = new Line(l, l2);
                lineList.add(line);
            }
        }
        return lineList;
    }


    // 判断点是否在矩形范围内
    public static boolean isWithinRectangle(double pointLat, double pointLon,
                                            double rectTopLeftLat, double rectTopLeftLon,
                                            double rectBottomRightLat, double rectBottomRightLon) {
        // 判断经度是否在矩形范围内
        if (pointLon < Math.min(rectTopLeftLon, rectBottomRightLon) ||
                pointLon > Math.max(rectTopLeftLon, rectBottomRightLon)) {
            return false;
        }
        // 判断纬度是否在矩形范围内
        return !(pointLat < Math.min(rectTopLeftLat, rectBottomRightLat)) &&
                !(pointLat > Math.max(rectTopLeftLat, rectBottomRightLat));
    }

    public static class Line {
        private Location location1;//起点
        private Location location2;//终点

        public Line(Location location1, Location location2) {
            this.location1 = location1;
            this.location2 = location2;
        }

        public Location getLocation1() {
            return location1;
        }

        public void setLocation1(Location location1) {
            this.location1 = location1;
        }

        public Location getLocation2() {
            return location2;
        }

        public void setLocation2(Location location2) {
            this.location2 = location2;
        }

        @Override
        public String toString() {
            return "Line{" +
                    "location1=" + location1 +
                    ", location2=" + location2 +
                    '}';
        }
    }

    public static class Location {

        private double longitude;//经度
        private double latitude;//纬度

        public Location(double longitude, double latitude) {
            this.longitude = longitude;
            this.latitude = latitude;
        }

        public double getLongitude() {
            return longitude;
        }

        public void setLongitude(double longitude) {
            this.longitude = longitude;
        }

        public double getLatitude() {
            return latitude;
        }

        public void setLatitude(double latitude) {
            this.latitude = latitude;
        }

        @Override
        public String toString() {
            return "Location{" +
                    "longitude=" + longitude +
                    ", latitude=" + latitude +
                    '}';
        }
    }

}
